package com.auxide.util;


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;

import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.Message;

public class NetworkManager {

	//Singleton pattern
	private NetworkManager(){};

	private static class NetworkManagerHolder {
		public static final NetworkManager instance = new NetworkManager();
	}

	public static NetworkManager getInstance(){
		return NetworkManagerHolder.instance;
	}


	Activity mActivity;
	ArrayList<String> connectedClients;
	Handler activityHandler;
	
	//server
	ServerSocket serverSocket;
	Thread serverThread;
	public boolean isServerRunning;
	public String SERVER_IP;
	public static final int SERVER_PORT = 6969;
	public static final int TIMEOUT = 10;
	
	//client
	Socket clientSocket;
	PrintWriter sendToServer;
	PrintWriter sendToClient;
	public boolean connectedToServer;

	public void init(Activity activity){
		this.mActivity 		   = activity;
		this.isServerRunning   = false;
		this.connectedToServer = false;
		this.connectedClients  = new ArrayList<String>();
	}

	//Server
	public String startServer(){
		if(isNetworkAvailable()){
			SERVER_IP = getLocalIpAddress();
			if(!isServerRunning){
				serverThread = new Thread(new ServerThread());
				serverThread.start();
			}
			return SERVER_IP;
		}
		else{
			return null;
		}
	}

	public void stopServer() {
		if(isServerRunning){
			isServerRunning = false;
			try {
				if(serverSocket != null){
					serverSocket.close();
				}
				//destroyThread();
			} catch (IOException e) {
				Logger.e(e);
			}
		}
	}
	
    public class ServerThread implements Runnable {
		public void run() {
			try {
				if (SERVER_IP != null) {
					handler.post(new Runnable() {
						@Override
						public void run() {
							Logger.log("[Server] Listening on IP: " + SERVER_IP);
						}
					});
					serverSocket = new ServerSocket(SERVER_PORT);
					while (true) {
						isServerRunning = true;
						// listen for incoming clients
						clientSocket = serverSocket.accept();//Blocking call
						clientConnected(clientSocket.getInetAddress().toString());
						try {
							BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
							String line = null;
							while ((line = in.readLine()) != null) {
								final String message = line;
								handler.post(new Runnable() {
									@Override
									public void run() {
										messageReceived(message);
									}
								});
							}
							break;
						} catch (Exception e) {
							Logger.e(e);
						}
					}
				} else {
					handler.post(new Runnable() {
						@Override
						public void run() {
							Logger.log("[Server] Couldn't detect internet connection.");
						}
					});
				}
			} catch (Exception e) {
				Logger.e(e);
			}
		}
	}

    //Client
    
    public boolean connectToServer(String serverIP, int port){
		if(!isNetworkAvailable()) return false;
		
		//Regex for ip
		//\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b

		Logger.log("[Client] Connecting to: " + serverIP);
		if (!connectedToServer) {
			if (!serverIP.equals("")) {
				Thread cThread = new Thread(new ClientThread(serverIP, port));
				cThread.start();
			}
		}

		for(int i = 0; i < TIMEOUT; i++){
			if(!connectedToServer){
				try {
					Logger.log("[Client] wait.. " + (TIMEOUT - i));
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					Logger.e(e);
				}
			}
			else{
				break;
			}
		}

		return connectedToServer;
	}

	public void disconnectoFromServer(){
		if (connectedToServer) {
			try {
				clientSocket.close();
				connectedToServer = false;
				sendToServer = null;
				Logger.log("[Client] Closed.");
			} catch (Exception e) {
				Logger.e(e);
			}
		}
	}
	
	public class ClientThread implements Runnable {
		String serverIP;
		int port;
		public ClientThread(String server, int port){
			this.serverIP = server;
			this.port = port;
		}
		public void run() {
			try {
				Logger.log("[Client] Connecting...");
				InetAddress serverAddr = InetAddress.getByName(serverIP);
				clientSocket = new Socket(serverAddr, port);
				connectedToServer = true;
				while (true) {
					try {
						BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
						String line = null;
						while ((line = in.readLine()) != null) {
							final String message = line;
							handler.post(new Runnable() {
								@Override
								public void run() {
									messageReceived(message);
								}
							});
						}
						break;
					} catch (Exception e) {
						Logger.e(e);
					}
				}
			} catch (Exception e) {
				Logger.e(e);
				connectedToServer = false;
			}
		}
	}

	public void setIncomingHandler(Handler handler){
		this.activityHandler = handler;
	}
	
	public ArrayList<String> getConnectedClients(){ return connectedClients; }
	
	public void sendMessage(String msg){
		Logger.log("[Client]  Sending msg.");
		if (connectedToServer || isServerRunning) {
			try {
				Logger.log("[Client]  Sending command.");
				if(sendToServer == null){
					sendToServer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())), true);
				}
				sendToServer.println(msg);
				Logger.log("[Client] Sent.");
			} catch (Exception e) {
				Logger.e(e);
			}
		}
	}

	public void sendMessageAsJSON(String name, String msg){
		JSONObject jo = new JSONObject();
		try{
			jo.put(name, msg);
		}catch(Exception e){
			Logger.e(e);
		}
		sendMessage(jo.toString());
	}

	public boolean isNetworkAvailable() {
		ConnectivityManager cm = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo networkInfo = cm.getActiveNetworkInfo();
		// if no network is available networkInfo will be null, otherwise check if we are connected
		return networkInfo != null && networkInfo.isConnected();
	}
	
	public String getLocalIpAddress() {
		try {
			for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
				NetworkInterface intf = en.nextElement();
				for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
					InetAddress inetAddress = enumIpAddr.nextElement();
					if((inetAddress instanceof Inet4Address) && !inetAddress.isLoopbackAddress())
					{
						return inetAddress.getHostAddress().toString(); //TODO: need to test this
					}
					//if (!inetAddress.isLoopbackAddress()) { return inetAddress.getHostAddress().toString(); }
				}
			}
		} catch (SocketException e) {
			Logger.e(e);
		}
		return null;
	}

	private void clientConnected(String client){
		final String connectedClient = client;
		handler.post(new Runnable() {
			@Override
			public void run() {
				connectedClients.add(connectedClient);
//				Message msg = getHandler().obtainMessage();
//				msg.arg2 = 1;
//				msg.obj = connectedClient;
//				activityHandler.sendMessage(msg);
				Logger.log("[Server] " + connectedClient + " Connected.");
			}
		});
	}
	
	private void messageReceived(String message){
		Message msg = getHandler().obtainMessage();
		if(message.startsWith("AddPlayer")){//add remove player?
			msg.arg2 = 1;
			msg.obj = message.split("&")[1];
		}else{
			msg.arg2 = 2;
			msg.obj = message;
		}
		
		activityHandler.sendMessage(msg);
	}
	
    public Handler getHandler(){ return handler; }
    private static Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch(msg.what){
                    case 1: //Message from Server received
                            //receive(msg.getData().getString("NetworkAction"));
                            break;
                    }
            }
    };
}
